/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/graphics/dx11.1/details/common.hpp>
#include <baja/graphics/dx11.1/graphics_fwd.hpp>
#include <baja/graphics/dx11.1/details/enum_mappings.hpp>
#include <baja/graphics/dx11.1/details/util.hpp>
#include <baja/graphics/dx11.1/details/math.hpp>

namespace d = baja::graphics::dx11_1::details;

namespace baja { namespace graphics { namespace dx11_1 {

// mesh implementation
class mesh : public imesh, public std::enable_shared_from_this<imesh>
{
private:
    // private types
    struct material
    {
        primitive_type primitiveType;
        int32 baseVertexIndex;
        uint32 minVertexIndex;
        uint32 vertexCount;
        uint32 indexBufferStart;
        uint32 primitiveCount;
        uint32 textureIndex;
        uint32 objectIndex;
        packed_color color;
    };

    // private data
    std::shared_ptr<ivertex_buffer> _vertexBuffer;
    std::shared_ptr<iindex_buffer> _indexBuffer;
    std::shared_ptr<imaterial_collection> _materialCollection;
    std::shared_ptr<icollision_resolver> _collisionResolver;
    std::shared_ptr<ivertex_declaration> _meshVertexDecl;

    uint32 _vertexCount;
    uint32 _indexCount;

    std::vector<material> _materials;

public:
    mesh() : _vertexCount(0), _indexCount(0) {}
    virtual ~mesh()
    {
        _vertexCount = 0;
        _indexCount = 0;

        _materials.clear();
    }

    // Creates a mesh with the specified geometry
    static std::shared_ptr<mesh> create(
        const std::shared_ptr<iengine>& graphics,
        const vertex_lit_colored_textured* vertices,
        const uint32 totalVerts,
        const uint16* indices,
        const uint32 totalIndices,
        const primitive_type primitiveType,
        const uint32 primitiveCount
        )
    {
        BAJA_CHECK_ARG(graphics.get());
        BAJA_CHECK_ARG(vertices);
        BAJA_CHECK_ARG(indices);

        BAJA_CHECK_ARG(totalVerts != 0);
        BAJA_CHECK_ARG(totalIndices != 0);
        BAJA_CHECK_ARG(primitiveCount != 0);

        // create the new mesh object
        std::shared_ptr<mesh> meshObject = std::make_shared<mesh>();

        // initialize counts & other fields
        meshObject->_vertexCount = totalVerts;
        meshObject->_indexCount = totalIndices;

        // initialize material data
        meshObject->_materials.resize(1);

        meshObject->_materials[0].primitiveType = primitiveType;
        meshObject->_materials[0].baseVertexIndex = 0;
        meshObject->_materials[0].minVertexIndex = 0;
        meshObject->_materials[0].vertexCount = totalVerts;
        meshObject->_materials[0].indexBufferStart = 0;
        meshObject->_materials[0].primitiveCount = primitiveCount;
        meshObject->_materials[0].textureIndex = 0;
        meshObject->_materials[0].objectIndex = 0;
        meshObject->_materials[0].color = packColor(0xff, 0xff, 0xff, 0xff);

        // create a default material
        std::shared_ptr<imaterial> material = createMaterial();

        // initialize material collection
        std::vector<std::shared_ptr<imaterial>> mats;
        mats.push_back(material);
        std::vector<std::string> matNames;
        matNames.push_back(std::string(""));

        meshObject->_materialCollection = createMaterialCollection(
            mats,
            matNames
            );

        meshObject->_vertexBuffer = graphics->createVertexBuffer(
            totalVerts * sizeof(vertex_lit_colored_textured),
            vertex_buffer_usage::dynamic,
            graphics
            );

        // create the index buffer
        meshObject->_indexBuffer = graphics->createIndexBuffer(totalIndices, index_buffer_usage::dynamic, graphics);

        // init vertex decl
        std::shared_ptr<iasset_manager> assetManager = graphics->getAssetManager();
        meshObject->_meshVertexDecl = assetManager->getVertexDeclaration(g::globals::standardVertexDeclName_Mesh);
        
        // copy the geometry
        meshObject->copyGeometry(vertices, indices);

        return meshObject;
    }

    std::shared_ptr<ivertex_buffer> vertexBuffer()
    {
        return _vertexBuffer;
    }

    std::shared_ptr<iindex_buffer> indexBuffer()
    {
        return _indexBuffer;
    }

    // Clones the material collection of this mesh
    std::shared_ptr<imaterial_collection> cloneMaterialCollection()
    {
        if (_materialCollection.get() != nullptr)
        {
            return _materialCollection->clone();
        }

        return std::shared_ptr<imaterial_collection>(); 
    }

    // Performs collision check of a line to the mesh
    bool lineHitTest(
        const m::line3d& line, 
        float32* intersectingT
        )
    {
        BAJA_CHECK_ARG(intersectingT);

        *intersectingT = 0.f;

        // assume hit test fails
        bool result = false;

        // use collision resolver 
        if (_collisionResolver.get() != nullptr)
        {
            result = _collisionResolver->lineToMeshHitTest(
                line,
                shared_from_this(),
                intersectingT
                );
        }

        return result;
    }

    // gets the collision resolver
    std::shared_ptr<icollision_resolver> getCollisionResolver()
    {
        return _collisionResolver;
    }

    // sets the collision resolve
    void setCollisionResolver(const std::shared_ptr<icollision_resolver>& collisionResolver)
    {
        _collisionResolver = collisionResolver;
    }
        
    // Render the mesh to the specified graphics device
    void render(
        const std::shared_ptr<iengine>& graphics, 
        const std::shared_ptr<imaterial_collection>& materials, 
        const m::matrix44* localToWorld, 
        const standard_effect standardEffect
        )
    {
        std::shared_ptr<ieffect2> effect;
        
        effect = graphics->getStandardEffect(standardEffect);

        this->renderWithEffect(
            graphics, 
            materials,
            localToWorld, 
            effect
            );
    }
    
    // Render the mesh to the specified graphics device using a custom effect
    void renderWithEffect(
        const std::shared_ptr<iengine>& graphics, 
        const std::shared_ptr<imaterial_collection>& materials, 
        const m::matrix44* localToWorld, 
        const std::shared_ptr<ieffect2>& effect
        )
    {
        BAJA_CHECK_ARG(effect.get());

        if (_materials.size() == 0)
        {
            // nothing to draw
            return; 
        }

        // TEST:
        std::shared_ptr<imaterial_collection> mats = materials;

        if (mats.get() == nullptr)
        {
            // render with default material collection
            mats = _materialCollection;
        }

        if (localToWorld != nullptr)
        {
            graphics->pushWorldMatrix(*localToWorld, false);
        }

        // set vertex decl 
        graphics->setVertexDeclaration(_meshVertexDecl); 

        uint32 i = 0;

        std::for_each(
            _materials.begin(),
            _materials.end(),
            [&](const material& mat)
            {
                // set the material 
                std::shared_ptr<imaterial> imat = mats->getMaterial(i);
                graphics->setMaterial(imat);

                // begin the effect
                effect->begin(graphics);

                effect->setSamplerState(L"linear_sampler");

                // set stream 0
                graphics->setStreamSource(
                    0, 
                    _vertexBuffer,
                    0,
                    sizeof(vertex_lit_colored_textured));

                // set index buffer
                graphics->setIndices(_indexBuffer);

                // draw
                graphics->drawIndexedPrimitive( 
                    mat.primitiveType,
                    mat.baseVertexIndex,
                    mat.minVertexIndex,
                    mat.vertexCount,
                    mat.indexBufferStart,
                    mat.primitiveCount
                    );
                
                // end effect
                effect->end(graphics);                

                i++; // TEST: need to make sure this entire loop works ok
            });

        // pop off world matrix
        if (localToWorld != nullptr)
        {
            graphics->popWorldMatrix();
        }
    }

    // copies the passed in geometry data into the mesh's vertex and index buffers
    void copyGeometry(
        const vertex_lit_colored_textured* vertexBuffer,
        const uint16* indexBuffer
        )
    {
        vertex_lit_colored_textured* vbData = nullptr;
        uint16* indices = nullptr;

        // lock the vertex buffer
        _vertexBuffer->lockDiscard((memory**)&vbData);
        
        // copy vertex data
        CopyMemory(vbData, vertexBuffer, _vertexCount * sizeof(vertex_lit_colored_textured));

        // unlock the vertex buffer
         _vertexBuffer->unlock();

        // lock index buffer
        _indexBuffer->lock((memory**)&indices);

        // copy index data
        CopyMemory(indices, indexBuffer, _indexCount * sizeof(uint16));

        // unlock index buffer
        _indexBuffer->unlock();
    }
}; 

}}} // namespace baja.graphics.dx11_1

namespace baja { namespace graphics {

// Creates a mesh object
inline std::shared_ptr<imesh> createMesh(
    const std::shared_ptr<iengine>& graphics,
    vertex_lit_colored_textured* vertices,
    const uint32 totalVerts,
    const uint16* indices,
    const uint32 totalIndices,
    const primitive_type primitiveType,
    const uint32 primitiveCount
    )
{
    // ensure tangent vectors are computed
    d::computeTangentVectorsForVertices(vertices, totalVerts, indices, totalIndices);

    return baja::graphics::dx11_1::mesh::create(
        graphics,
        vertices,
        totalVerts,
        indices,
        totalIndices,
        primitiveType,
        primitiveCount
        );
}

}} // namespace baja::graphics